import { useCallback } from "react";

interface TextNode {
  detail: number;
  format: number;
  mode: string;
  style: string;
  text: string;
  type: "custom-text";
  version: number;
}

interface VariableNode {
  type: "variable-value";
  version: number;
  variable: string;
}

type ParsedNode = TextNode | VariableNode;

const VARIABLE_PATTERN = /\{\{#([^#}]+)#\}\}/g;

const createTextNode = (text: string): TextNode => ({
  detail: 0,
  format: 0,
  mode: "normal",
  style: "",
  text,
  type: "custom-text",
  version: 1,
});

const createVariableNode = (variable: string): VariableNode => ({
  type: "variable-value",
  version: 1,
  variable,
});

function parseTextWithVariables(text: string): ParsedNode[] {
  if (!text) {
    return [createTextNode("")];
  }

  const matches = Array.from(text.matchAll(VARIABLE_PATTERN));

  if (matches.length === 0) {
    return [createTextNode(text)];
  }

  return matches.reduce<ParsedNode[]>((nodes, match, index) => {
    const matchStart = match.index ?? 0;
    const prevMatchEnd =
      index === 0
        ? 0
        : (matches[index - 1].index ?? 0) + matches[index - 1][0].length;

    // Add text before the current variable (if any)
    if (matchStart > prevMatchEnd) {
      const textBefore = text.slice(prevMatchEnd, matchStart);
      nodes.push(createTextNode(textBefore));
    }

    // Add the variable node
    nodes.push(createVariableNode(match[1]));

    // Add remaining text after the last variable
    if (index === matches.length - 1) {
      const remainingStart = matchStart + match[0].length;
      if (remainingStart < text.length) {
        const remainingText = text.slice(remainingStart);
        nodes.push(createTextNode(remainingText));
      }
    }

    return nodes;
  }, []);
}

interface ParagraphNode {
  children: ParsedNode[];
  direction: string;
  format: string;
  indent: number;
  type: "paragraph";
  version: number;
}

interface RootNode {
  children: ParagraphNode[];
  direction: string;
  format: string;
  indent: number;
  type: "root";
  version: number;
}

const createParagraphNode = (children: ParsedNode[]): ParagraphNode => ({
  children,
  direction: "ltr",
  format: "",
  indent: 0,
  type: "paragraph",
  version: 1,
});

const createRootNode = (children: ParagraphNode[]): RootNode => ({
  children,
  direction: "ltr",
  format: "",
  indent: 0,
  type: "root",
  version: 1,
});

export function textJsonToEditorState(value: string | null): string {
  const paragraphs = value?.split("\n") ?? [""];

  const paragraphNodes = paragraphs.map((paragraph) =>
    createParagraphNode(parseTextWithVariables(paragraph)),
  );

  const editorState = createRootNode(paragraphNodes);

  return JSON.stringify({ root: editorState });
}

export interface MenuTextMatch {
  leadOffset: number;
  matchingString: string;
  replaceableString: string;
}

/**
 * Create a matcher callback that triggers when the input text ends with a
 * double opening brace "{{". Useful for opening a variable/command menu in a
 * prompt editor as soon as the user types two braces.
 *
 * Behavior:
 * - If the given `text` does NOT end with "{{", returns `null` (no match).
 * - If it DOES end with "{{", returns a `MenuTextMatch` indicating:
 *   - `leadOffset`: index where the trigger starts (text.length - 2)
 *   - `matchingString`: always an empty string for this simple trigger
 *   - `replaceableString`: "{{" (the trigger text)
 */
export function triggerMatchForDoubleBrace() {
  return useCallback((text: string): MenuTextMatch | null => {
    if (!text.endsWith("{{")) return null;
    const leadOffset = text.length - 2;
    return {
      leadOffset,
      matchingString: "",
      replaceableString: "{{",
    };
  }, []);
}

/**
 * Converts preset JSON editor state to plain text for comparison
 * This function extracts all text content and variables from a Lexical editor state JSON
 */
export function convertPresetJsonToText(jsonState: string): string {
  const parsed = JSON.parse(jsonState);
  const root = parsed.root;

  if (!root?.children || !Array.isArray(root.children)) return "";

  return (
    root.children
      // biome-ignore lint/suspicious/noExplicitAny: unknown type
      .map((paragraph: any) => {
        if (!paragraph.children || !Array.isArray(paragraph.children))
          return "";

        return (
          paragraph.children
            // biome-ignore lint/suspicious/noExplicitAny: unknown type
            .map((node: any) => {
              if (node.type === "custom-text") return node.text || "";
              if (node.type === "variable-value")
                return `{{#${node.variable}#}}`;
              return "";
            })
            .join("")
        );
      })
      .join("\n")
  );
}

/**
 * Creates a fast lookup function for matching plain text to preset keys
 * Uses pre-computed text cache for optimal performance
 */
export function createPresetMatcher(presetTextCache: Map<string, string>) {
  return (currentValue: string | null): string => {
    if (!currentValue) return "";

    for (const [key, presetText] of presetTextCache) {
      if (presetText === currentValue) return key;
    }
    return "";
  };
}
